﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;
using System.IO;
using System.Text.RegularExpressions;
using SWCInvoice.Model;
using Cyotek.GhostScript.PdfConversion;
using Cyotek.GhostScript;
using SWCInvoice.Returntyps;
using System.Collections.ObjectModel;

namespace SWCInvoice.Servicelayer {
    public class PDFHandler {


        public static int GetPageCount(string fileName) {
            PdfReader pdfReader = new PdfReader(fileName);
            return pdfReader.NumberOfPages;
        }
        /// <summary>
        /// Extract a PDF file
        /// </summary>
        /// <param name="fileName">The filename of the PDF file</param>
        /// <returns>A ExtractPDFInfo object with the Virtual Document Structure which contains all the PDF information</returns>        
        public static ExtractPDFInfo ExtractPDF(string fileName) {
            ExtractPDFInfo extractPDFInfo = new ExtractPDFInfo(fileName);
            try {
                VirtualDocumentStructure vds = new VirtualDocumentStructure(fileName);
                
                PdfReader reader = new PdfReader(fileName);

                //Pdf2ImageSettings pdf2ImageSettings = GetPdf2ImageSettings();
                //Pdf2Image pdf2Image = new Pdf2Image(fileName, pdf2ImageSettings);


                Document document = new Document(vds);
                document.IsOriginal = true;
                document.ReadOnly = true;
                PdfSmartCopy pdfWriter = new PdfSmartCopy(new iTextSharp.text.Document(), null);
                Page page = null;

                for (int i = 1; i <= reader.NumberOfPages; i++) {
                    page = new Page(document);
                    page.Content = PdfTextExtractor.GetTextFromPage(reader, i);
                    page.PageNumberOriginal = i;
                    page.PDFContent = pdfWriter.GetImportedPage(reader, i);

                    //page.ImageContent = CreateImage(pdf2Image, i);

                    document.Pages.Add(page);
                }

                vds.Documents.Add(document);


                extractPDFInfo.PdfNumberOfPages = document != null ? document.PageCount : 0;
                extractPDFInfo.VirtualDocumentStructure = vds;

            } catch (Exception ex) {

                extractPDFInfo.ExceptionList.Add(ex);
            }

            return extractPDFInfo;
        }

        /// <summary>
        /// Create a pdf 2 image setting set
        /// </summary>
        /// <returns>A Pdf2ImageSettings</returns>
        public static Pdf2ImageSettings GetPdf2ImageSettings()
        {
            Pdf2ImageSettings settings = new Pdf2ImageSettings();
            settings.AntiAliasMode = SWCInvoiceSettings.GetPreviewImageAntiAliasMode();//AntiAliasMode.High;//AntiAliasMode.High;
            settings.Dpi = SWCInvoiceSettings.GetPreviewImageDpi();//300;
            settings.GridFitMode = GridFitMode.Topological;
            settings.ImageFormat = SWCInvoiceSettings.GetPreviewImageFormat();//ImageFormat.Png24;
            settings.TrimMode = PdfTrimMode.CropBox;

            return settings;
        }

        /// <summary>
        /// Create a bitmap of a page
        /// </summary>
        /// <param name="pdf2Image"></param>
        /// <param name="page"></param>
        /// <returns></returns>
        public static Bitmap CreateImage(Pdf2Image pdf2Image, int page)
        {
            return pdf2Image.GetImage(page);
        }

        /// <summary>
        /// Create all documents from the virtual document structure
        /// </summary>
        /// <param name="vds">The virtual document structure</param>
        /// <param name="withoutOriginalDocuments">Create only documents with the the isOriginal flag = false</param>
        /// <returns>A CreatePDFInfo object</returns>
        public static CreatePDFInfo CreatePDF(VirtualDocumentStructure vds, bool withoutOriginalDocuments = true) {
            CreatePDFInfo createPDFInfo = new CreatePDFInfo();

            try {
                FileStream outputStream = null;
                iTextSharp.text.Document pdfDocument = null;
                PdfCopy pdfWriter = null;
                

                List<Document> documents = null;
                if (withoutOriginalDocuments)
                    documents = vds.Documents.ToList<Document>().FindAll(p => !p.IsOriginal);
                else
                    documents = vds.Documents.ToList<Document>().FindAll(p => p.IsOriginal || !p.IsOriginal);

                int tmpPageCount = 0;
                foreach (Document document in documents) {
                    pdfDocument = new iTextSharp.text.Document();
                    outputStream = GetNewPDFFileStream(string.Format("{0}\\{1}\\{2}.pdf", vds.ResultFullFolderPath, document.DocumentPathName, document.DocumentName));                    
                    pdfWriter = new PdfCopy(pdfDocument, outputStream);                    
                    pdfWriter.CompressionLevel = PdfStream.BEST_COMPRESSION;
                    pdfDocument.Open();

                    tmpPageCount += document.PageCount;
                    foreach (Page page in document.Pages) {
                        pdfWriter.AddPage(page.PDFContent);
                    }
                    pdfDocument.Close();
                    pdfWriter.Close();
                }

                createPDFInfo.FolderName = vds.ResultFullFolderPath;
                createPDFInfo.DocumentCount = documents.Count;
                createPDFInfo.PageCount = tmpPageCount;
                createPDFInfo.VdsName = vds.VDSName;

            } catch (Exception ex) {

                createPDFInfo.ExceptionList.Add(ex);
            }
            return createPDFInfo;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="vds">The virtual document structure</param>
        /// <returns>A MergeDocumentsInfo object</returns>
        public static MergeDocumentsInfo MergeDocuments(VirtualDocumentStructure vds) {
            MergeDocumentsInfo mergeDocumentsInfo = new MergeDocumentsInfo();            
            int tmpProcessedPageCount = 0;

            List<Document> documents = vds.Documents.ToList<Document>().FindAll(p => !p.IsOriginal);
            try {
                Document newDocument = null;
                
                int[] identifier = documents.Select(p => p.PageCount).Distinct().ToArray();

                foreach (int identifierItem in identifier) {
                    var tmpDocuments = from d in documents where d.PageCount == identifierItem select d;

                    newDocument = new Document(vds, identifierItem.ToString());
                    newDocument.IsMerged = true;                    

                    foreach (Document itemDocument in tmpDocuments.ToList<Document>()) {
                        
                        tmpProcessedPageCount += itemDocument.PageCount;

                        foreach (Page itemPage in itemDocument.Pages) {
                            newDocument.Pages.Add(itemPage);
                        }
                    }
                    vds.Documents.Add(newDocument);

                    mergeDocumentsInfo.Documents.Add(new KeyValuePair<string,int>(newDocument.DocumentName, newDocument.PageCount));
                }

                
            } catch (Exception ex) {

                mergeDocumentsInfo.ExceptionList.Add(ex);
            }
            
            mergeDocumentsInfo.ProcessedPageCount = tmpProcessedPageCount;

            return mergeDocumentsInfo;
        }

        /// <summary>
        /// Split the document into parts defined by the identifier
        /// </summary>
        /// <param name="baseDocument">The base (original) document</param>
        /// <returns>A SplitDocumentByIdentifierInfo object</returns>
        public static SplitDocumentByIdentifierInfo SplitDocumentByIdentifier(Document baseDocument) {
            SplitDocumentByIdentifierInfo splitDocumentByIdentifierInfo = new SplitDocumentByIdentifierInfo();
            int tmpProcessedPageCount = 0;

            try {
                Document newDocument = null;
                VirtualDocumentStructure vds = baseDocument.VirtualDocumentStructure;
                string[] identifier = baseDocument.Pages.Select(p => p.Identifier).Distinct().ToArray();

                foreach (string identifierItem in identifier) {
                    var pages = from p in baseDocument.Pages where p.Identifier == identifierItem orderby p.PageNumberOriginal ascending select p;

                    newDocument = new Document(vds, identifierItem);
                    newDocument.IsPlain = true;                    

                    //won't work with ObservablesCollections
                    //newDocument.Pages.ToList<Page>().AddRange(pages.ToList<Page>());
                    foreach (Page item in pages.ToList<Page>()) {
                        newDocument.Pages.Add(item);
                        tmpProcessedPageCount++;
                    }
                    vds.Documents.Add(newDocument);
                    
                    splitDocumentByIdentifierInfo.Documents.Add(new KeyValuePair<string, int>(newDocument.DocumentName, newDocument.PageCount));
                }
            } catch (Exception ex) {

                splitDocumentByIdentifierInfo.ExceptionList.Add(ex);
            }
            return splitDocumentByIdentifierInfo;
        }

        /// <summary>
        /// Set the identifier for each page over an identifierKey and a regex search pattern
        /// </summary>
        /// <param name="vds">The virtual document structure</param>
        /// <param name="identifierKey">The primary identifier key</param>
        /// <param name="pattern">The regex search pattern</param>
        /// <param name="inheritance">Inherited the last valid identifier when no identifier was found</param>
        /// <param name="additional">Additional only search for elements which have no identifier</param>
        /// <returns>A SetPageIdentifierInfo object</returns>
        public static SetPageIdentifierInfo SetPageIdentifier(VirtualDocumentStructure vds, string identifierKey, string pattern, bool inheritance = false, bool additional = false) {
            SetPageIdentifierInfo setPageIdentifierInfo = new SetPageIdentifierInfo(identifierKey, pattern, inheritance);

            string lastIdentifier = string.Empty;
            string tmpIdentifier = string.Empty;
            List<Document> documents = null;
            documents = vds.Documents.ToList<Document>().FindAll(p => p.IsOriginal || !p.IsOriginal);

            if (documents != null)
                setPageIdentifierInfo.DocumentCount = documents.Count;                

            Regex regex = new Regex(pattern);
            foreach (Document document in documents) {
                setPageIdentifierInfo.PageCount += document.PageCount;

                lastIdentifier = string.Empty;
                foreach (Page page in document.Pages) {
                    if (!additional || (additional && page.HasIdentifier != Utils.Utils.IdentifierState.Identifier)) {
                        if (!string.IsNullOrWhiteSpace(identifierKey)) {
                            tmpIdentifier = page.SetIdentifier(identifierKey, regex);
                        } else {
                            tmpIdentifier = page.SetIdentifier(regex);
                        }

                        if (inheritance && string.IsNullOrWhiteSpace(tmpIdentifier)) {
                            page.SetIdentifier(lastIdentifier);
                            page.HasIdentifierInheritance = true;
                        } else
                            lastIdentifier = tmpIdentifier;

                        if (!string.IsNullOrWhiteSpace(page.Identifier))
                            setPageIdentifierInfo.IdentifierCount++;
                    }
                }   
            }

            return setPageIdentifierInfo;
        }

        /// <summary>
        /// Delete the identifier of each page
        /// </summary>
        /// <param name="vds">The virtual document structure</param>
        /// <returns>A DeletePageIdentifierInfo object</returns>
        public static DeletePageIdentifierInfo DeletePageIdentifier(VirtualDocumentStructure vds) {
            DeletePageIdentifierInfo deletePageIdentifierInfo = new DeletePageIdentifierInfo();

            try {
                List<Document> documents = null;
                documents = vds.Documents.ToList<Document>().FindAll(p => p.IsOriginal || !p.IsOriginal);

                int tmpPageCount = 0;
                int tmpIdentifierCount = 0;
                foreach (Document document in documents) {
                    tmpPageCount += document.PageCount;
                    foreach (Page page in document.Pages) {
                        page.SetIdentifier(string.Empty);
                        page.HasIdentifierInheritance = false;

                        tmpIdentifierCount++;
                    }
                }

                deletePageIdentifierInfo.PageCount = tmpPageCount;
                deletePageIdentifierInfo.IdentifierCount = tmpIdentifierCount;

            } catch (Exception ex) {

                deletePageIdentifierInfo.ExceptionList.Add(ex);
            }

            return deletePageIdentifierInfo;
        }

        /// <summary>
        /// Set the identifier for each document over a regex search pattern
        /// </summary>
        /// <param name="vds">The virtual document structure</param>
        /// <param name="pattern">The regex search pattern</param>
        /// <returns>A SetDocumentSortIdentifierInfo object</returns>
        public static SetDocumentSortIdentifierInfo SetDocumentSortIdentifier(VirtualDocumentStructure vds, string pattern) {
            SetDocumentSortIdentifierInfo setDocumentSortIdentifierInfo = new SetDocumentSortIdentifierInfo(pattern);

            try {

                List<Document> documents = null;
                documents = vds.Documents.ToList<Document>();

                MatchCollection matchCollection = null;

                Regex searchPattern = new Regex(pattern);

                int tmpDocumentCount = 0;
                int tmpSortIdentifierCount = 0;

                foreach (Document item in documents) {
                    tmpDocumentCount++;
                    if (!item.IsOriginal) {
                        //item.SortIdentifier = item.Pages[0].Identifier;

                        if (searchPattern.IsMatch(item.Pages[0].Content)) {
                            tmpSortIdentifierCount++;
                            matchCollection = searchPattern.Matches(item.Pages[0].Content);
                            item.SortIdentifier = matchCollection[0].Value;
                        }
                    }
                }

                setDocumentSortIdentifierInfo.DocumentCount = tmpDocumentCount;
                setDocumentSortIdentifierInfo.IdentifierCount = tmpSortIdentifierCount;

                vds.Documents = new ObservableCollection<Document>(vds.Documents.OrderBy(item => item.SortIdentifier));
            } catch (Exception ex) {

                setDocumentSortIdentifierInfo.ExceptionList.Add(ex);
            }

            return setDocumentSortIdentifierInfo;
        }

        /// <summary>
        /// Add a empty page to  a document
        /// </summary>
        /// <param name="vds">The virtual document structure</param>
        /// <param name="oddPages">When true: Add a empty page when the document had odd number of pages</param>
        /// <param name="evenPages">When true: Add a empty page when the document had even number of pages</param>   
        /// <returns>A AddEmptyPageInfo object</returns>
        public static AddEmptyPageInfo AddEmptyPage(VirtualDocumentStructure vds, bool oddPages, bool evenPages, bool withoutOriginalDocuments = true) {
            AddEmptyPageInfo addEmptyPageInfo = new AddEmptyPageInfo();            

            try {
                List<Document> documents = null;

                if (withoutOriginalDocuments)
                    documents = vds.Documents.ToList<Document>().FindAll(p => !p.IsOriginal);
                else
                    documents = vds.Documents.ToList<Document>().FindAll(p => p.IsOriginal || !p.IsOriginal);

                //List<Document> tmpDocuments = documents.FindAll(p => !p.ReadOnly); //(from p in documents where !p.ReadOnly select p).ToList<Document>();

                int tmpPageCount = 0;
                foreach (Document document in documents) {
                    if (oddPages) {
                        if (document.PageCount > 0 && document.PageCount % 2 == 1) {
                            document.AddEmptyPage();

                            tmpPageCount++;
                        }
                    }
                    if (evenPages) {
                        if (document.PageCount > 0 && document.PageCount % 2 == 0) {
                            document.AddEmptyPage();

                            tmpPageCount++;
                        }
                    }
                }

                addEmptyPageInfo.PageCount = tmpPageCount;
            } catch (Exception ex) {

                addEmptyPageInfo.ExceptionList.Add(ex);
            }

            return addEmptyPageInfo;
        }

        /// <summary>
        /// Create a new file stream from a given filename
        /// </summary>
        /// <param name="fileName">The filename from the file</param>
        /// <returns>the new file stream</returns>
        private static FileStream GetNewPDFFileStream(string fileName) {
            FileStream outputStream = new FileStream(fileName, FileMode.Create);
            return outputStream;        
        }
    }
}
